# Copyright 2020 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from abc import abstractmethod
import json
import random
import asyncio
import logging
from connector_common.connector_interface \
    import BlockchainConnectorInterface
from avalon_sdk.connector.direct.jrpc.jrpc_worker_registry \
    import JRPCWorkerRegistryImpl
from avalon_sdk.connector.direct.jrpc.jrpc_work_order \
    import JRPCWorkOrderImpl
from connector_common.worker_delegate import \
    WorkerDelegate
from connector_common.work_order_delegate import \
    WorkOrderDelegate

logging.basicConfig(
    format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO)

# Poll interval to update workers on chain with those in kv store
WORKER_REFRESH_INTERVAL = 30


class BaseConnector(BlockchainConnectorInterface):

    """
    This abstract base class. Bridge between the blockchain and the Avalon
    core. It listens for events generated by the blockchain.
    It handles event data corresponding to the event (eg: workOrderSubmitted
    and submits requests to Avalon on behalf of the client. The service also
    invokes smart contract API (eg: workOrderComplete).
    """

    def __init__(self, config, registry_instance, worker_instance,
                 work_order_instance, wo_receipt_instance):
        """
        Initialize the connector with instances of registry,
        worker, work order and work order receipt implementation
        classes of blockchains
        @param config - dict containing connector configurations.
        @param registry_instance - implementation class object for registry
        @param worker_instance - implementation class object for worker
        @param work_order_instance - implementation class object for work order
        @param wo_receipt_instance - implementation class object for work order
        receipt
        """
        self._registry_instance = registry_instance
        self._wo_receipt_instance = wo_receipt_instance

        # JSON RPC based worker and work order instances
        jprc_worker_instance = JRPCWorkerRegistryImpl(config)
        jrpc_work_order_instance = JRPCWorkOrderImpl(config)

        self._worker_delegate = WorkerDelegate(
            config,
            jprc_worker_instance,
            worker_instance
        )
        self._work_order_delegate = WorkOrderDelegate(
            jrpc_work_order_instance,
            work_order_instance
        )
        # List of active available worker ids in Avalon
        self._active_worker_ids = []

    def sync_registries(self):
        """
        Synchronize registries between blockchain and avalon
        """
        # TODO need to use registries.
        logging.info("Not implemented yet")

    async def sync_workers(self):
        """
        Sync up workers between blockchain and avalon
        """
        # Fetch first worker details from shared KV (via direct API)
        # and add the worker to block chain.
        # TODO: Fetch all workers from blockchain and update kv store
        # That will be a 2-way sync. As of now, only one way.
        while True:
            try:
                self._active_worker_ids = \
                    self._worker_delegate.lookup_workers_in_kv_storage()
                # Lookup worker ids from blockchain
                chain_worker_ids = self._worker_delegate.\
                    lookup_workers_onchain()
                # Add/Update workers to chain
                self._worker_delegate.add_update_worker_to_chain(
                    self._active_worker_ids,
                    chain_worker_ids)
                # Sleep for 30 seconds and poll again
                await asyncio.sleep(WORKER_REFRESH_INTERVAL)
            except Exception as ex:
                logging.exception("Exception occurred during worker sync-up {}"
                                  .format(ex))

    async def sync_work_orders(self):
        """
        Sync up work orders between blockchain and avalon
        """
        self.start_wo_submitted_event_listener(
            self.work_order_submitted_event_handler)

    def sync_work_order_receipts(self):
        """
        Sync up work order receipts between blockchain and avalon
        """
        # TODO need to be implemented receipt
        logging.info("Not implemented yet!!")

    def work_order_submitted_event_handler(self, work_order_id, worker_id,
                                           requester_id, work_order_params):
        """
        Handler function to be called from event listener
        after getting an event.
        This function submits work order to listener, gets
        result and stores work order result to blockchain.
        @param work_order_id - id of work order got from the event
        @param worker_id - id of worker got from the event.
        @param requester_id - requester id who submitted request
        @param work_order_params - work order parameters.
        """
        logging.info("Got work order request {} from blockchain".format(
            work_order_params
        ))
        # Submit work order request if request has active
        # worker id
        if worker_id in self._active_worker_ids:
            response = self._work_order_delegate.\
                submit_work_order_and_get_result(
                    work_order_id, worker_id,
                    requester_id, work_order_params)
            if response:
                self._work_order_delegate.add_work_order_result_to_chain(
                    work_order_id, response)
            else:
                logging.info("Work order processing failed!")

    def start(self):
        """
        Function to start the connector
        """
        event_loop = asyncio.get_event_loop()
        logging.info("Blockchain Connector service started")
        event_loop.run_until_complete(self.sync())

    async def sync(self):
        """
        Wrapper function to wait for the coroutines to finish. It simply
        waits for the sync_workers() & sync_work_orders() to complete.
        """
        await asyncio.gather(self.sync_workers(), self.sync_work_orders())
